#include "OpenGLRenderer.h"
#include "../core/AutoReleasePool.h"
#include "./OpenGL/OpenGLShader.h"
#include "./OpenGL/OpenGLShaderProgram.h"
#include "../core/materials/ShaderMaterial.h"
#include "../core/materials/MeshNormalMaterial.h"
#include "../objects/Mesh.h"
#include "./OpenGL/OpenGLRenderData.h"
#include "./OpenGL/OpenGLUtils.h"
#include "./OpenGL/shaders/ShaderLib.h"

POCELAIN_NAMESPACE_BEGIN

//enum ECommonUniform
//{
//    E_CU_MODEL_MATRIX4 = 0,
//    E_CU_VIEW__MATRIX4 ,
//    E_CU_MODEL_VIEW_MATRIX4 ,
//    E_CU_PROJECTION_MATRIX4 ,
//    E_CU_NORMAL_MATRIX3 ,
//    E_CU_CAMERA_POSITION_VEC3 ,
//    E_CU_IS_ORTHOGRAPHIC_BOOL ,

//    E_CU_END
//};

OpenGLRenderer::OpenGLRenderer(GLFWwindow *window,InputGLFW *input)
    :mWindow(window)
    ,mInput(input)
{
    mFnGeoDataCallback =
            new TFunction<void(IRenderData*)>(
                std::bind(
                    &OpenGLRenderer::geometryRenderDataDestructCallback,
                    this,std::placeholders::_1));
    mFnMatDataCallback =
            new TFunction<void(IRenderData*)>(
                std::bind(
                    &OpenGLRenderer::materialRenderDataDestructCallback,
                    this,std::placeholders::_1));
    mMapCommonUniform = {
        {"modelMatrix",RefPtr<IUniform>(CreateRef<Uniform<Matrix4>>())},
        {"viewMatrix",RefPtr<IUniform>(CreateRef<Uniform<Matrix4>>())},
        {"projectionMatrix",RefPtr<IUniform>(CreateRef<Uniform<Matrix4>>())},
        {"modelViewMatrix",RefPtr<IUniform>(CreateRef<Uniform<Matrix4>>())},
        {"normalMatrix",RefPtr<IUniform>(CreateRef<Uniform<Matrix3>>())},
        {"cameraPosition",RefPtr<IUniform>(CreateRef<Uniform<Vector3>>())},
        {"isOrthographic",RefPtr<IUniform>(CreateRef<Uniform<bool>>())},
    };
    std::transform(
        mMapCommonUniform.begin(),
        mMapCommonUniform.end(),
        std::inserter(mMapCommonUniformType,mMapCommonUniformType.begin()),
        [](std::pair<std::string,RefPtr<IUniform>>p){
            return std::pair<std::string,EUniformType>(
                p.first,
                p.second->getUniformType()
                );
        });
}
OpenGLRenderer::~OpenGLRenderer()
{
    for(auto &it : mMapRenderDataVao)
    {
        it.first.geometryRenderData->mEventDestructCallback -= mFnGeoDataCallback;
        it.first.materialRenderData->mEventDestructCallback -= mFnMatDataCallback;
    }

    if(mFnGeoDataCallback)
    {
        mFnGeoDataCallback->release();
    }
    if(mFnMatDataCallback)
    {
        mFnMatDataCallback->release();
    }
}
void OpenGLRenderer::render(Scene *scene, Camera *camera)
{
    AutoReleasePool();

    // update matrix
    scene->updateMatrixWorld();

    // camera setup
    if(camera->getParent()!=nullptr)
    {
        camera->updateMatrixWorld();
    }
    {
        // Camera uniform
//        Matrix4 modelWorldMatrix;
        Matrix4 projectionMatrix = camera->getProjectionMatrix();
        Matrix4 viewMatrix = camera->getMatrixWorldInverse();
        bool isOrthorgraphic = camera->isOrthographicCamera();

//        auto &uModelViewMatrix =
//            dynamic_cast<Uniform<Matrix4>&>(mMapCommonUniform["modelViewMatrix"]);
//        auto &uNormalMatrix =
//            dynamic_cast<Uniform<Matrix4>&>(mMapCommonUniform["normalMatrix"]);
        auto uCameraPosition =
            dynamic_cast<Uniform<Vector3>*>(mMapCommonUniform["cameraPosition"].get());
        auto uViewMatrix =
            dynamic_cast<Uniform<Matrix4>*>(mMapCommonUniform["viewMatrix"].get());
        auto uProjectionMatrix =
            dynamic_cast<Uniform<Matrix4>*>(mMapCommonUniform["projectionMatrix"].get());
        auto uIsOrthorgraphic =
            dynamic_cast<Uniform<bool>*>(mMapCommonUniform["isOrthographic"].get());
        uCameraPosition->setValue(camera->getPosition());
        uViewMatrix->setValue(viewMatrix);
        uProjectionMatrix->setValue(projectionMatrix);
        uIsOrthorgraphic->setValue(isOrthorgraphic);
    }

    // light setup
    setupLights(scene);

    // compile
    compile(scene);

    // sort
    mRenderQueue.clear();
    sort(scene,camera);

    // clear
    clearAllBuffer();

    // render
    renderByQueue(mRenderQueue);
    
    // swap buffer
    glfwSwapBuffers(this->mWindow);
}
void OpenGLRenderer::sort(Node3D *node,Camera *camera)
{
    if(node->isMesh())
    {
        auto mesh = (Mesh*)node;
        auto material = mesh->getMaterial();
        auto geometry = mesh->getGeometry();

        RenderQueueItem item ;
        item.geometry = geometry;
        item.material = material;
        item.modelMatrix = mesh->getMatrixWorld();
        mRenderQueue.push_back(item);
    }
    for(auto &child : node->getChildren())
    {
        sort(child,camera);
    }
}
void setUniform(GLuint location , IUniform *uniform)
{
    GLenum type = GL_FLOAT;

    switch(uniform->getUniformType())
    {
//    //boolean
//    case EUniformType::E_UNIFORM_BOOL:
//        type = GL_BOOL;
//        break;
//        // integer
//    case EUniformType::E_UNIFORM_BYTE:
//        type = GL_BYTE;
//        break;
//    case EUniformType::E_UNIFORM_UBYTE:
//        type = GL_UNSIGNED_BYTE;
//        break;
//    case EUniformType::E_UNIFORM_SHORT:
//        type = GL_SHORT;
//        break;
//    case EUniformType::E_UNIFORM_USHORT:
//        type = GL_UNSIGNED_SHORT;
//        break;
    case EUniformType::E_UNIFORM_INT:
    {
        type = GL_INT;
        auto newUniform = static_cast<Uniform<int>*>(uniform);
        glUniform1i(location,newUniform->getValue());
        break;
    }
    case EUniformType::E_UNIFORM_UINT:
    {
        type = GL_UNSIGNED_INT;
        auto newUniform = static_cast<Uniform<uint32_t>*>(uniform);
        glUniform1ui(location,newUniform->getValue());
        break;
    }
    case EUniformType::E_UNIFORM_FLOAT:
    {
        type = GL_FLOAT;
        auto newUniform = static_cast<Uniform<float>*>(uniform);
        glUniform1f(location,newUniform->getValue());
        break;
    }
//    case EUniformType::E_UNIFORM_DOUBLE:
//        type = GL_DOUBLE;
//        break;
//        //vector
    case EUniformType::E_UNIFORM_VECTOR2:
    {
        type = GL_FLOAT_VEC2;
        auto newUniform = static_cast<Uniform<Vector2>*>(uniform);
        glUniform2fv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_VECTOR3:
    {
        type = GL_FLOAT_VEC3;
        auto newUniform = static_cast<Uniform<Vector3>*>(uniform);
        glUniform3fv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_VECTOR4:
    {
        type = GL_FLOAT_VEC4;
        auto newUniform = static_cast<Uniform<Vector4>*>(uniform);
        glUniform4fv(location,1,newUniform->getValue().elements.data());
        break;
    }
//    case EUniformType::E_UNIFORM_DVECTOR2:
//        type = GL_DOUBLE_VEC2;
//        break;
//    case EUniformType::E_UNIFORM_DVECTOR3:
//        type = GL_DOUBLE_VEC3;
//        break;
//    case EUniformType::E_UNIFORM_DVECTOR4:
//        type = GL_DOUBLE_VEC4;
//        break;
    case EUniformType::E_UNIFORM_IVECTOR2:
    {
        type = GL_INT_VEC2;
        auto newUniform = static_cast<Uniform<IVector2>*>(uniform);
        glUniform2iv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_IVECTOR3:
    {
        type = GL_INT_VEC3;
        auto newUniform = static_cast<Uniform<IVector3>*>(uniform);
        glUniform3iv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_IVECTOR4:
    {
        type = GL_INT_VEC4;
        auto newUniform = static_cast<Uniform<IVector4>*>(uniform);
        glUniform4iv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_UVECTOR2:
    {
        type = GL_UNSIGNED_INT_VEC2;
        auto newUniform = static_cast<Uniform<UVector2>*>(uniform);
        glUniform2uiv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_UVECTOR3:
    {
        type = GL_UNSIGNED_INT_VEC3;
        auto newUniform = static_cast<Uniform<UVector3>*>(uniform);
        glUniform3uiv(location,1,newUniform->getValue().elements.data());
        break;
    }
    case EUniformType::E_UNIFORM_UVECTOR4:
    {
        type = GL_UNSIGNED_INT_VEC4;
        auto newUniform = static_cast<Uniform<UVector4>*>(uniform);
        glUniform4uiv(location,1,newUniform->getValue().elements.data());
        break;
    }
//    case EUniformType::E_UNIFORM_BVECTOR2:
//        type = GL_BOOL_VEC2;
//        break;
//    case EUniformType::E_UNIFORM_BVECTOR3:
//        type = GL_BOOL_VEC3;
//        break;
//    case EUniformType::E_UNIFORM_BVECTOR4:
//        type = GL_BOOL_VEC4;
//        break;
    //matrix
    case EUniformType::E_UNIFORM_MATRIX2x2:
    {
        type = GL_FLOAT_MAT2;
        auto newUniform = static_cast<Uniform<Matrix2>*>(uniform);
        glUniformMatrix2fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX2x3:
    {
        type = GL_FLOAT_MAT2x3;
        auto newUniform = static_cast<Uniform<Matrix2x3>*>(uniform);
        glUniformMatrix2x3fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX2x4:
    {
        type = GL_FLOAT_MAT2x4;
        auto newUniform = static_cast<Uniform<Matrix2x4>*>(uniform);
        glUniformMatrix2x4fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX3x2:
    {
        type = GL_FLOAT_MAT3x2;
        auto newUniform = static_cast<Uniform<Matrix3x2>*>(uniform);
        glUniformMatrix3x2fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX3x3:
    {
        type = GL_FLOAT_MAT3;
        auto newUniform = static_cast<Uniform<Matrix3>*>(uniform);
        glUniformMatrix3fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX3x4:
    {
        type = GL_FLOAT_MAT3x4;
        auto newUniform = static_cast<Uniform<Matrix3x4>*>(uniform);
        glUniformMatrix3x4fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX4x2:
    {
        type = GL_FLOAT_MAT4x2;
        auto newUniform = static_cast<Uniform<Matrix4x2>*>(uniform);
        glUniformMatrix4x2fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX4x3:
    {
        type = GL_FLOAT_MAT4x3;
        auto newUniform = static_cast<Uniform<Matrix4x3>*>(uniform);
        glUniformMatrix4x3fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
    case EUniformType::E_UNIFORM_MATRIX4x4:
    {
        type = GL_FLOAT_MAT4;
        auto newUniform = static_cast<Uniform<Matrix4>*>(uniform);
        glUniformMatrix4fv(location,1,GL_TRUE,newUniform->getValue().elements);
        break;
    }
//    case EUniformType::E_UNIFORM_DMATRIX2x2:
//        type = GL_DOUBLE_MAT2;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX2x3:
//        type = GL_DOUBLE_MAT2x3;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX2x4:
//        type = GL_DOUBLE_MAT2x4;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX3x2:
//        type = GL_DOUBLE_MAT3x2;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX3x3:
//        type = GL_DOUBLE_MAT3;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX3x4:
//        type = GL_DOUBLE_MAT3x4;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX4x2:
//        type = GL_DOUBLE_MAT4x2;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX4x3:
//        type = GL_DOUBLE_MAT4x3;
//        break;
//    case EUniformType::E_UNIFORM_DMATRIX4x4:
//        type = GL_DOUBLE_MAT4;
//        break;
    default:
        POCELAIN_ASSERT(false);
        break;
    }
    POCELAIN_OPENGL_CHECK_ERROR("gl set uniforms");
}
void OpenGLRenderer::setupLights(Node3D *node)
{
//    if(node->isMesh())
//    {
//        auto mesh = (Mesh*)node;
//        auto material = mesh->getMaterial();
//        if(material->getDirty())
//        {
//            reconstructRenderData(material);
//        }
//        auto geometry = mesh->getGeometry();
//        if(geometry->getDirty())
//        {
//            reconstructRenderData(geometry);
//        }

//        //TODO : dynamic_cast ?
//        auto geoData = static_cast<OpenGLGeometryRenderData*>(getRenderData(geometry));
//        auto matData = static_cast<OpenGLMaterialRenderData*>(getRenderData(material));

//        makeVAO(geoData,matData);

//    }
//    for(auto &child : node->getChildren())
//    {
//        compile(child);
//    }
}
void OpenGLRenderer::compile(Node3D *node)
{
    if(node->isMesh())
    {
        auto mesh = (Mesh*)node;
        auto geometry = mesh->getGeometry();
        if(geometry->getDirty())
        {
            reconstructRenderData(geometry);
        }
        //TODO : dynamic_cast ?
        auto geoData = static_cast<OpenGLGeometryRenderData*>(getRenderData(geometry));

        auto material = mesh->getMaterial();
        auto shaderMat = dynamic_cast<RawShaderMaterial*>(material);
        if(shaderMat->getDirty())
        {
            reconstructRenderData(shaderMat,geoData->mMapNameType);
        }

        auto matData = static_cast<OpenGLMaterialRenderData*>(getRenderData(material));

        makeVAO(geoData,matData);

    }
    for(auto &child : node->getChildren())
    {
        compile(child);
    }
}

RefPtr<OpenGLVertexArrayObject> OpenGLRenderer::getVAO(
    OpenGLGeometryRenderData *geometryData,
    OpenGLMaterialRenderData *materialData
)
{
    GeometryMaterialItem item(geometryData,materialData);

    auto it = mMapRenderDataVao.find(item);

    if(it==mMapRenderDataVao.end())
    {
        POCELAIN_ASSERT(false);
    }
    return it->second;
}

void OpenGLRenderer::renderByQueue(RenderQueue_t &queue)
{
    CommonRenderState commonRenderState;
    if(queue.size()==0)
    {
        return;
    }
//    else
//    {
//        auto &material = queue.front().material;
//        commonRenderState.colorWrite = material->getColorWrite();
//        commonRenderState.depthTest = material->getDepthTest();
//        commonRenderState.depthWrite = material->getDepthWrite();
//        commonRenderState.alphaTest = material->getAlphaTest();

////        commonRenderState.stencilWrite = material->getStencilWrite();
////        commonRenderState.stencilFunc= material->getStencilFunc();
////        commonRenderState.stencilFuncMask = material->getStencilFuncMask();
////        commonRenderState.stencilWriteMask = material->getStencilWriteMask();
////        commonRenderState.stencilFail = material->getStencilFail();
////        commonRenderState.stencilZFail = material->getStencilZFail();
////        commonRenderState.stencilZPass = material->getStencilZPass();

//        commonRenderState.blendDst = material->getBlendDst();
//        commonRenderState.blendSrc = material->getBlendSrc();
//        commonRenderState.blending = material->getBlending();
//        commonRenderState.blendEquation = material->getBlendEquation();
//        commonRenderState.side = material->getSide();
//    }

    for(auto &it:queue)
    {
        auto geometry = it.geometry;
        auto material = it.material;
        const auto &modelMatrix = it.modelMatrix;

        // state set
        {
            if(commonRenderState.colorWrite != material->getColorWrite())
            {
                commonRenderState.colorWrite = material->getColorWrite();
                //TODO implement?
            }
            if(commonRenderState.depthTest != material->getDepthTest())
            {
                commonRenderState.depthTest = material->getDepthTest();
                if(commonRenderState.depthTest)
                {
                    glEnable(GL_DEPTH_TEST);
                }
                else
                {
                    glDisable(GL_DEPTH_TEST);
                }
            }
            if(commonRenderState.depthWrite != material->getDepthWrite())
            {
                commonRenderState.depthWrite = material->getDepthWrite();
                if(commonRenderState.depthWrite)
                {
                    glDepthMask(GL_TRUE);
                }
                else
                {
                    glDepthMask(GL_FALSE);
                }
            }

            //            if(commonRenderState.alphaTest != material->getAlphaTest())
            //            {
            //                commonRenderState.alphaTest = material->getAlphaTest();
            //            }

            //        commonRenderState.stencilWrite = material->getStencilWrite();
            //        commonRenderState.stencilFunc= material->getStencilFunc();
            //        commonRenderState.stencilFuncMask = material->getStencilFuncMask();
            //        commonRenderState.stencilWriteMask = material->getStencilWriteMask();
            //        commonRenderState.stencilFail = material->getStencilFail();
            //        commonRenderState.stencilZFail = material->getStencilZFail();
            //        commonRenderState.stencilZPass = material->getStencilZPass();

            if(commonRenderState.blending != material->getBlending())
            {
                commonRenderState.blending = material->getBlending();
            }
            if(commonRenderState.blendDst != material->getBlendDst())
            {
                commonRenderState.blendDst = material->getBlendDst();
            }
            if(commonRenderState.blendSrc != material->getBlendSrc())
            {
                commonRenderState.blendSrc = material->getBlendSrc();
            }
            if(commonRenderState.blendEquation != material->getBlendEquation())
            {
                commonRenderState.blendEquation = material->getBlendEquation();
            }
            if(commonRenderState.side != material->getSide())
            {
                commonRenderState.side = material->getSide();
            }
        }

        // set common uniform
        {
            auto uViewMatrix =
                dynamic_cast<Uniform<Matrix4>*>(mMapCommonUniform["viewMatrix"].get());
            const auto &viewMatrix = uViewMatrix->getValue();
            auto uModelMatrix =
                dynamic_cast<Uniform<Matrix4>*>(mMapCommonUniform["modelMatrix"].get());
            auto uModelViewMatrix =
                dynamic_cast<Uniform<Matrix4>*>(mMapCommonUniform["modelViewMatrix"].get());
            auto uNormalMatrix =
                dynamic_cast<Uniform<Matrix3>*>(mMapCommonUniform["normalMatrix"].get());

            Matrix4 modelViewMatrix = viewMatrix*modelMatrix;
            Matrix3 normalMatrix;
            normalMatrix.getNormalMatrix(modelViewMatrix);
            uModelMatrix->setValue(modelMatrix);
            uModelViewMatrix->setValue(modelViewMatrix);
            uNormalMatrix->setValue(normalMatrix);
        }

        //TODO : dynamic_cast ?
        auto geoData = static_cast<OpenGLGeometryRenderData*>(getRenderData(geometry));
        auto matData = static_cast<OpenGLMaterialRenderData*>(getRenderData(material));

        auto ebo = geoData->mEBO;
        auto vao = getVAO(geoData,matData);

        auto program = matData->getProgram();
        program->use();
        auto &uniformMap = matData->getLocationUniformMap();

        for(auto &kv : uniformMap)
        {
            auto location = kv.first;
            auto uniform = kv.second;

            setUniform(location,uniform);
        }

        vao->bind();
        size_t size =geometry->getIndices()->getDataArraySize();
        glDrawElements(
                    GL_TRIANGLES,
                    size,
                    ebo->getGLType(),
                    (void*)0);
        POCELAIN_OPENGL_CHECK_ERROR("glDrawElements");
        ++commonRenderState.drawCount;
        vao->unbind();
    }
}

//void OpenGLRenderer::setupLight2(Light *light)
//{
//    switch(light->getObjectClass())
//    {
//    case E_AMBIENT_LIGHT:
//    {
//        break;
//    }
//    case E_DIRECTIONAL_LIGHT:
//    {
//        break;
//    }
////    case E_POINT_LIGHT:
////    {
////        break;
////    }
////    case E_SPOT_LIGHT:
////    {
////        break;
////    }
//    default:
//        POCELAIN_ASSERT(false);
//        POCELAIN_LOG_ERROR("Not Support Type Of Light");
//        break;
//    }
//}

void OpenGLRenderer::makeVAO( OpenGLGeometryRenderData *geometryData, OpenGLMaterialRenderData *materialData)
{
    POCELAIN_ASSERT(geometryData&&materialData);

    GeometryMaterialItem item(geometryData,materialData);

    auto it = mMapRenderDataVao.find(item);

    if(it!=mMapRenderDataVao.end())
    {
        return;
    }

    RefPtr<OpenGLVertexArrayObject> pVAO(new OpenGLVertexArrayObject);

    pVAO->bind();
    geometryData->mEBO->bind();
    auto program = materialData->getProgram();
    //TODO need use?
    program->use();
    for(auto &kv : geometryData->mMapNameBuffer)
    {
        auto attributeName = kv.first;
        auto buffer = kv.second;

        auto location = program->getAttributeLocation(attributeName);

        // location not exists in shader
        if(location == -1)
            continue;

        program->enableVertexAttributeArray(location);

        buffer->bind();

        glVertexAttribPointer(
                    location,
                    buffer->getitemSize(),
                    buffer->getGLType(),
                    buffer->getNormalized()?GL_TRUE:GL_FALSE,
                    0,
                    (void*)0
                    );
        POCELAIN_OPENGL_CHECK_ERROR("glVertexAttribPointer");
    }
    pVAO->unbind();

    // binding
    geometryData->mEventDestructCallback+= mFnGeoDataCallback;
    materialData->mEventDestructCallback+= mFnMatDataCallback;
    mMapRenderDataVao.insert(
                std::make_pair(item,pVAO)
                );
}

void OpenGLRenderer::geometryRenderDataDestructCallback(IRenderData *renderData)
{
    if(renderData==nullptr)
        return;
    for(auto it = mMapRenderDataVao.begin();
        it!=mMapRenderDataVao.end();
        )
    {
        auto &gmItem = it->first;
        if(gmItem.geometryRenderData == renderData)
        {
            gmItem.materialRenderData->mEventDestructCallback-=mFnMatDataCallback;
            it = mMapRenderDataVao.erase(it);
        }
        ++it;
    }
}

void OpenGLRenderer::materialRenderDataDestructCallback(IRenderData *renderData)
{
    if(renderData==nullptr)
        return;
    for(auto it = mMapRenderDataVao.begin();
        it!=mMapRenderDataVao.end();
        )
    {
        auto &gmItem = it->first;
        if(gmItem.materialRenderData == renderData)
        {
            gmItem.geometryRenderData->mEventDestructCallback-=mFnGeoDataCallback;
            it = mMapRenderDataVao.erase(it);
        }
        ++it;
    }
}

OpenGLGeometryRenderData *OpenGLRenderer::reconstructRenderData(BufferGeometry *geometry)
{
    auto renderData = (OpenGLGeometryRenderData *)getRenderData(geometry);

    if(renderData==nullptr)
    {
        renderData = CreateRef<OpenGLGeometryRenderData>();
    }

    //indices
    auto ebo = renderData->mEBO;
    if(ebo == nullptr)
    {
        ebo = CreateRef<OpenGLBuffer>();
        renderData->mEBO = ebo;
    }
    auto indices = geometry->getIndices();
    ebo->setTarget(EOpenGLBufferTarget::ElementArrayBuffer);

    ebo->setUsagePattern(EOpenGLBufferUsagePattern::StaticDraw);
    ebo->setItemSize(indices->getItemSize());
    ebo->setGLTypeByUniformType(indices->getItemType());
    ebo->write(indices->getDataArray(),indices->getDataArrayByteSize());

    auto &attributs = geometry->getNameAttributeMap();
    for(auto &kv : attributs)
    {
        auto &name = kv.first;
        auto &attribute = kv.second;
        auto vbo = RefPtr<OpenGLBuffer>(new OpenGLBuffer);
        vbo->setTarget(EOpenGLBufferTarget::ArrayBuffer);
        vbo->setUsagePattern(EOpenGLBufferUsagePattern::StaticDraw);
        vbo->write(attribute->getDataArray(),attribute->getDataArrayByteSize());
        vbo->setItemSize(attribute->getItemSize());
        vbo->setNormalzied(attribute->getNormalized());
        vbo->setGLTypeByUniformType(attribute->getItemType());
        renderData->mMapNameBuffer.insert(std::make_pair(name,vbo));
        renderData->mMapNameType.insert(std::make_pair(name,attribute->getAttributeType()));
    }

    clearGeometryDirty(geometry);
    setRenderData(geometry,renderData);

    return renderData;

}

void OpenGLRenderer::loadShaderFromLib(RawShaderMaterial *mat)
{
    switch(mat->getObjectClass())
    {
    case EObjectClass::E_MESHNORMAL_MATERIAL:
    {
        auto &it = ShaderLib::getShaderItemByName("normal");

        mat->shaderSource(EShaderType::E_VERTEX,it.vertexShader);
        mat->shaderSource(EShaderType::E_FRAGMENT,it.fragmentShader);
        break;
    }
    case EObjectClass::E_SHADER_MATERIAL:
    case EObjectClass::E_RAW_SHADER_MATERIAL:
        break;
    default:
        POCELAIN_LOG_FATAL("Not support mat");
        abort();
    }
}
OpenGLMaterialRenderData *OpenGLRenderer::reconstructRenderData(
    RawShaderMaterial *shaderMaterial,
    const std::map<std::string,EUniformType> &attributeTypeMap)
{
    OpenGLShader *vertShader = nullptr;
    OpenGLShader *fragShader = nullptr;
    OpenGLShader *tessShader = nullptr;
    OpenGLShader *geomShader = nullptr;

    auto renderData = static_cast<OpenGLMaterialRenderData*>(getRenderData(shaderMaterial)) ;

    if(renderData == nullptr)
    {
        renderData = CreateRef<OpenGLMaterialRenderData>();
        setRenderData(shaderMaterial,renderData);
    }
    auto shaderProgram = CreateRef<OpenGLShaderProgram>();

    loadShaderFromLib(shaderMaterial);
    auto source = shaderMaterial->getShaderSource(EShaderType::E_VERTEX);
    if(source.size())
    {
        renderData->mVertShaderSource = source;
        vertShader = CreateRef<OpenGLShader>(EShaderType::E_VERTEX);
        vertShader->resetByString(source);
    }

    source = shaderMaterial->getShaderSource(EShaderType::E_FRAGMENT);
    if(source.size())
    {
        renderData->mFragShaderSource = source;
        fragShader = CreateRef<OpenGLShader>(EShaderType::E_FRAGMENT);
        fragShader->resetByString(source);
    }

    source = shaderMaterial->getShaderSource(EShaderType::E_TESSELATION);
    if(source.size())
    {
        renderData->mTessShaderSource = source;
        tessShader = CreateRef<OpenGLShader>(EShaderType::E_TESSELATION);
        tessShader->resetByString(source);
    }

    source = shaderMaterial->getShaderSource(EShaderType::E_GEOMETRY);
    if(source.size())
    {
        renderData->mGeomShaderSource = source;
        geomShader = CreateRef<OpenGLShader>(EShaderType::E_GEOMETRY);
        geomShader->resetByString(source);
    }

    if(shaderMaterial->getObjectClass()==EObjectClass::E_RAW_SHADER_MATERIAL)
    {
        std::map<std::string,std::string> defines;
        if(!shaderProgram->linkProgram(
                tessShader,
                geomShader,
                vertShader,
                fragShader,
                defines,
                std::map<std::string,EUniformType>(),
                decltype(mMapCommonUniformType)()
                ))
        {
            POCELAIN_LOG_ERROR("Program link error");
        }
    }
    else
    {
        std::map<std::string,std::string> defines = {
            {"NUM_CLIPPING_PLANES","0"}
        };
        if(!shaderProgram->linkProgram(
                tessShader,
                geomShader,
                vertShader,
                fragShader,
                defines,
                attributeTypeMap,
                mMapCommonUniformType
                ))
        {
            POCELAIN_LOG_ERROR("Program link error");
        }

        std::map<GLint,IUniform*> uniformLocationMap;

        //lights
        for(auto &kv : mMapName2LightUniform)
        {
            auto &uniformName = kv.first;
            auto uniform = kv.second;

            auto location = shaderProgram->getUniform(uniformName);

            if(location==-1)
            {
                continue;
            }

            renderData->addUniform(location,uniform.get());
        }

        // common uniform
        for(auto &kv : mMapCommonUniform)
        {
            auto &uniformName = kv.first;
            auto uniform = kv.second;

            auto location = shaderProgram->getUniform(uniformName);

            if(location==-1)
            {
                // Don't worry
                // if glsl shader declared uniform ,
                // but not used in any code ,uniform will not be active
                POCELAIN_LOG_WARN("No active uniform named : %s\n",uniformName.c_str());
                continue;
            }

            renderData->addUniform(location,uniform.get());
        }
    }

    //customize uniform
    auto &customizedUniform = shaderMaterial->getNameUniformMap();
    for(auto &kv : customizedUniform)
    {
        auto &uniformName = kv.first;
        auto uniform = kv.second;

        auto location = shaderProgram->getUniform(uniformName);

        if(location==-1)
        {
            POCELAIN_LOG_WARN("No uniform name : %s",uniformName.c_str());
            continue;
        }

        renderData->addUniform(location,uniform.get());
    }

    renderData->setProgram(shaderProgram);
    clearMaterialDirty(shaderMaterial);
    return renderData;

    return nullptr;
}

void OpenGLRenderer::clearBuffer(ERenderBufferType bufferType)
{
    switch(bufferType)
    {
    case ERenderBufferType::E_BUFFER_COLOR:
        {
            glClearColor(mClearColor.r,mClearColor.g,mClearColor.b,mClearAlpha);
            glClear(GL_COLOR_BUFFER_BIT);
            break;
        }
    case ERenderBufferType::E_BUFFER_DEPTH:
        {
            glClear(GL_DEPTH_BUFFER_BIT);
            break;
        }
    case ERenderBufferType::E_BUFFER_STENCIL:
        {
            glClear(GL_STENCIL_BUFFER_BIT);
            break;
        }
    default:
        POCELAIN_LOG_ERROR("Unknown bufferType");
        break;
    }
    POCELAIN_OPENGL_CHECK_ERROR("glClearColor");
}

POCELAIN_NAMESPACE_END
